package microprocessador;

import main.Constante;

import componentes.Registrador;

import excecao.ExcecaoRegistrador;
import excecao.ExcecaoULA;

public class ULA {
	
	public static final int C = 15;
	public static final int Z = 16;
	public static final int N = 17;
	public static final int P = 18;
	public static final int O = 19;	
	private static final int TAM_ENDERECO = 20;
	private static final int TAM_INSTRUCAO = 4;
	
	private String operandoA;
	private String operandoB;
	private String codOperacao;
	private String saida;
	private Registrador regFlag;
	
	// Definicao do Tamanho da Entrada do Operando e da Instrucao
	public ULA(){
		this.operandoA = Constante.LIXO.getValor();
		this.operandoB = Constante.LIXO.getValor();
		this.codOperacao = Constante.LIXO.getValor();
		this.saida = Constante.LIXO.getValor();
		
		this.regFlag = new Registrador("F");
	}	
	
	public String getOperandoA(){
		return this.operandoA;
	}		

	// Carrega o Primeiro Operador
	public void setOperandoA(String operandoA) {
		this.operandoA = geraComplemento(operandoA.length()) + operandoA;
	}
	
	public String getOperandoB(){
		return this.operandoB;
	}	
	
	// Carrega o Segundo Operador
	public void setOperandoB(String operandoB) {
		this.operandoB = geraComplemento(operandoB.length()) + operandoB;
	}	
	
	public String getCodOperacao() {
		return this.codOperacao;
	}
	
	// Define o Cogido de Operacao
	public void setCodOperacao(String codOperacao) throws ExcecaoULA {
		if (codOperacao.length() == TAM_INSTRUCAO){
			this.codOperacao = codOperacao;
		}
		else{
			throw new ExcecaoULA("Instrução muito grande!");
		}
	}
	
	public String getSaida(){
		return this.saida;
	}
	
	// Completa a palavra se caso possuir um numero de bits menor do que o definido
	public static String geraComplemento(int size){
		String complemento = "";
		
		for(int i=0; i < TAM_ENDERECO - size; i++){
			complemento += "0";
		}
		
		return complemento;
	}
		
	public void executaInstrucao() throws ExcecaoRegistrador{
		// OPA
		if(this.codOperacao.equals("0000")){
			this.saida = this.operandoA;
		}
		// OPB
		else if(this.codOperacao.equals("0001")){
			this.saida = this.operandoB;
		}
		// INC OPA
		else if(this.codOperacao.equals("0010")){
			this.saida = this.INC_OPA();
		}
		// INC sem F
		else if(this.codOperacao.equals("0011")){
			this.saida = this.INCF_OPA();
		}		
		// ADD -> OPA + OPB
		else if(this.codOperacao.equals("0100")){
			this.saida = this.OPA_ADD_OPB();
		}
		// DEC OPA
		else if(this.codOperacao.equals("0101")){
			this.saida = this.DEC_OPA();
		}
		// DEC sem F
		else if(this.codOperacao.equals("0110")){
			this.saida = this.DECF_OPA();
		}		
		// SUB -> OPA - OPB
		else if(this.codOperacao.equals("0111")){
			this.saida = this.OPA_SUB_OPB();
		}		
		// OPA AND OPB
		else if (this.codOperacao.equals("1000")){
			this.saida = this.OPA_AND_OPB();
		}		
		// OPA OR OPB
		else if (this.codOperacao.equals("1001")){
			this.saida = this.OPA_OR_OPB();
		}		
		// NOT OPA
		else if (this.codOperacao.equals("1010")){
			this.saida = this.NOT_OPA();
		}
		// NEG OPA
		else if(this.codOperacao.equals("1011")){
			this.saida = this.NEG_OPA();
		}		
		// CLR OPA
		else if(this.codOperacao.equals("1100")){
			this.saida = this.CLR();
		}
		// SHL OPA
		else if(this.codOperacao.equals("1101")){
			this.saida = this.SHL_OPA();
		}
		// SHR OPA
		else if(this.codOperacao.equals("1110")){
			this.saida = this.SHR_OPA();
		}
		// F
		else if(this.codOperacao.equals("1111")){
			this.saida = this.regFlag.getRegistro();
		}		
	}
	
	public Registrador getFlags(){
		return this.regFlag;
	}
	
	private String INC_OPA() throws ExcecaoRegistrador{	
		this.setOperandoB("1");
		String result = this.soma(this.operandoA, this.operandoB);
		
		return result;
	}	
	
	private String INCF_OPA() throws ExcecaoRegistrador{
		this.setOperandoB("1");
		String s = this.somaNotFlag(this.operandoB, this.operandoA);
		
		return s;
	}	
	
	private String OPA_ADD_OPB() throws ExcecaoRegistrador{
		return this.soma(this.operandoA, this.operandoB);
	}	
	
	private String DEC_OPA() throws ExcecaoRegistrador{
		this.setOperandoB("1");
		this.operandoB = complementoA2(this.operandoB);
		String s = this.soma(this.operandoA, this.operandoB);
		return s;
	}
	
	private String DECF_OPA() throws ExcecaoRegistrador{
		this.setOperandoB("1");
		this.operandoB = complementoA2(this.operandoB);
		String s = this.somaNotFlag(this.operandoA, this.operandoB);
		return s;
	}	
	
	private String OPA_SUB_OPB() throws ExcecaoRegistrador{
		this.operandoB = complementoA2(this.operandoB);
		return this.soma(this.operandoA, this.operandoB);		
	}	
	
	private String OPA_AND_OPB() throws ExcecaoRegistrador{
		char[] a = this.getOperandoA().toCharArray();
		char[] b = this.getOperandoB().toCharArray();
		char flags[] = this.regFlag.getRegistro().toCharArray();
		
		flags[C] = '1';
		flags[Z] = '1';
		
		String r = "";
		for (int i=TAM_ENDERECO - 1; i >=0; i--){
			if((a[i]=='1') && (b[i]=='1')){
				r = "1" + r;
				flags[Z] = '0';
				flags[P] = (flags[P] == '0' ? '1' : '0');
			}
			else{
				r = "0" + r;
			}
		}
		
		flags[N] = a[0];
		
		// sempre 0
		flags[O] = '0';
		flags[C] = '0';
		
		this.regFlag.setRegistro(String.valueOf(flags));
		
		return r;
	}
	
	private String OPA_OR_OPB() throws ExcecaoRegistrador{
		char[] a = this.getOperandoA().toCharArray();
		char[] b = this.getOperandoB().toCharArray();
		char[] flags = this.regFlag.getRegistro().toCharArray();
		
		flags[Z] = '1';
		flags[P] = '1';
		
		String r = "";
		for (int i=TAM_ENDERECO - 1; i >=0; i--){
			if((a[i]=='0') && (b[i]=='0')){
				r = "0" + r;
			}
			else{
				r = "1" + r;
				flags[Z] = '0';
				flags[P] = (flags[P] == '0' ? '1' : '0');
			}
		}
		
		
		flags[N] = a[0];
		
		// sempre 0
		flags[O] = '0';
		flags[C] ='0';
		
		this.regFlag.setRegistro(String.valueOf(flags));
		
		return r;
	}
	
	private String NOT_OPA(){
		char[] a = this.getOperandoA().toCharArray();
		
		for (int i=0; i < TAM_ENDERECO; i++){
			a[i] = (a[i] == '0') ? '1' : '0';
		}
		return String.valueOf(a);
	}		
	
	private String NEG_OPA() throws ExcecaoRegistrador{
		this.setOperandoB("0");
		this.operandoA = complementoA2(this.operandoA);
		return this.soma(this.operandoB, this.operandoA);		
	}
	
	private String CLR(){
		String r = "";
		for (int i=0; i < TAM_ENDERECO; i++){
			r += "0";
		}
		
		return r;
	}	
	
	private String SHL_OPA() throws ExcecaoRegistrador{
		char[] a = this.getOperandoA().toCharArray();
		char flags[] = this.regFlag.getRegistro().toCharArray();
		
		flags[P] = '1';
		flags[Z] = '1';
		
		flags[C] = a[0];
		for (int i=1; i < TAM_ENDERECO; i++){
			a[i-1] = a[i];
			if (a[i-1] == '1'){
				flags[Z] = '0';
				flags[P] = (flags[P] == '0' ? '1' : '0');
			}
		}
		a[TAM_ENDERECO-1] = '0';
		
		if (a[0] != flags[C]){
			flags[O] = '1';
		} else {
			flags[O] = '0';
		}
		flags[N] = a[0];
		
		this.regFlag.setRegistro(String.valueOf(flags));
		
		return String.valueOf(a);		
	}
	
	private String SHR_OPA() throws ExcecaoRegistrador{
		char[] a = this.getOperandoA().toCharArray();
		char[] flags = this.regFlag.getRegistro().toCharArray();
		
		flags[P] = '1';
		flags[Z] = '1';
		
		flags[C] = a[0];
		for (int i=TAM_ENDERECO-2; i>=0; i--){
			a[i+1] = a[i];
			if (a[i+1] == '1'){
				flags[Z] ='0';
				flags[P] = (flags[P] == '0' ? '1' : '0');
			}
		}
		a[0] = '0';
		
		if (a[0] != flags[C]){
			flags[O] = '1';
		} else {
			flags[O] = '0';
		}
		flags[N] = a[0];
		
		this.regFlag.setRegistro(String.valueOf(flags));
		
		return String.valueOf(a);		
	}	
	
	public static String complementoA2(String s){
		s = geraComplemento(s.length()) + s;
		
		char[] a = s.toCharArray();
		
		int i = s.length()-1;
		while (a[i] != '1'){
			i--;
			if (i < 0){
				break;
			}
		}
		
		i--;
		for(; i>=0; i--){
			a[i] = (a[i] == '0') ? '1' : '0';
		}
		
		return String.valueOf(a);
	}
	
	private String soma(String s1, String s2) throws ExcecaoRegistrador{ 
		char[] a = s1.toCharArray();
		char[] b = s2.toCharArray();
		char[] flags = this.regFlag.getRegistro().toCharArray();
		String saida = "";
		
		flags[P] = '0';
		flags[Z] = '1';
		flags[C] = '0';
		flags[N] = '0';
				
		// soma com carry
		for(int i=TAM_ENDERECO-1; i>0; i--){
			if (flags[C] == '0'){
				if (a[i] != b[i]){
					saida = "1" + saida;
					flags[P] = (flags[P] == '0' ? '1' : '0');
					flags[Z] = '0';
				}
				else if(a[i] == '1'){
					flags[C] = '1';
					saida = "0" + saida;
				}
				else{
					saida = "0" + saida;
				}
			}
			else {
				if (a[i] != b[i]){
					saida = "0" + saida;
				}
				else if(a[i] == '0'){
					flags[C] = '0';
					saida = "1" + saida;
					flags[P] =(flags[P] == '0' ? '1' : '0');
					flags[Z] = '0';
				}
				else{
					saida = "1" + saida;
					flags[P] = (flags[P] == '0' ? '1' : '0');
					flags[Z] = '0';
				}
			}
		}
		
		// bit mais significativo separado, para testar o overflow
		if (flags[C] == '0'){
			if (a[0] != b[0]){
				saida = "1" + saida;
				flags[P] = (flags[P] == '0' ? '1' : '0');
				flags[Z] = '0';
				flags[N] = '1';
			}
			else if(a[0] == '1'){
				flags[C] = '1';
				flags[O] = '1';
				saida = "0" + saida;
				flags[N] = '0';
			}
			else{
				saida = "0" + saida;
				flags[N] = '0';
			}
		}
		else {
			if (a[0] != b[0]){
				saida = "0" + saida;
				flags[N] = '0';
			}
			else if(a[0] == '0'){
				flags[C] = '0';
				saida = "1" + saida;
				flags[P] = (flags[P] == '0' ? '1' : '0');
				flags[Z] = '0';
				flags[O] = '1';
				flags[N] = '1';
			}
			else{
				saida = "1" + saida;
				flags[P] = (flags[P] == '0' ? '1' : '0');
				flags[Z] = '0';
				flags[N] = '1';
			}
		}
		
		this.regFlag.setRegistro(String.valueOf(flags));
		
		return saida;
	}
	
	private String somaNotFlag(String s1, String s2) throws ExcecaoRegistrador{ 
		char[] a = s1.toCharArray();
		char[] b = s2.toCharArray();
		char[] flags = this.regFlag.getRegistro().toCharArray();
		String saida = "";
				
		flags[C] = '0';
		
		// soma com carry
		for(int i=TAM_ENDERECO-1; i>=0; i--){
			if (flags[C] == '0'){
				if (a[i] != b[i]){
					saida = "1" + saida;
				}
				else if(a[i] == '1'){
					saida = "0" + saida;
					flags[C] = '1';
				}
				else{
					saida = "0" + saida;
				}
			}
			else {
				if (a[i] != b[i]){
					saida = "0" + saida;
				}
				else if(a[i] == '0'){
					saida = "1" + saida;
					flags[C] = '0';
				}
				else{
					saida = "1" + saida;
				}
			}
		}
		
		this.regFlag.setRegistro(String.valueOf(flags));
		
		return saida;
	}
}